package console.manager;

import java.util.Vector;
import java.lang.Exception;
import javax.swing.SwingUtilities;
import java.io.PrintStream;
import java.io.FileOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Calendar;
import java.text.SimpleDateFormat;
import java.awt.Container;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;

import console.ConsoleException;

class Task extends Container {
  int timeRunning;
  int cpuTime;
  Calendar timeStart;
  Calendar timeEnd;

  Exception exception;
  TaskProcess taskProcess;
  File logFile=null;

  final int taskId;
  String name;
  String stateInfo;
  int progress;
  int progressTotal;
  Object lock;
  Thread thread;
  int request;
  int state;
  int parentTaskId;
  boolean relyOnParent;
  boolean subTask;
  String errorMessages;

  Task(String name,int taskId) {
    this.name = name;
    this.taskId = taskId;
    timeRunning = 0;
    request = TaskManager.REQUEST_RUN;
    state = TaskManager.STATE_RUN;
    lock = new Object();
    parentTaskId = 0;
    timeStart=null;
    timeEnd=null;
    registerListeners();
    clearErrorMessages();
//    for(PropertyChangeListener pcl :TaskManager.listenerList) {
//      addPropertyChangeListener(pcl);
//    }
  }
  int getTaskId() {
    return(taskId);
  }
  void setRequest(int request) {
    this.request = request;
/*
    synchronized(lock) {
      if(stateIn==TaskManager.RUNNING_STATE) {
        lock.notifyAll();
      }
    }
*/
  }
  int getRequest() {
    return(request);
  }
  void setState(int state) {
    int prev = this.state;
    log(TaskManager.stateName(state));
    this.state = state;
    if((state==TaskManager.STATE_DONE)||(state==TaskManager.STATE_ERROR)) {
      timeEnd=Calendar.getInstance();
    }
    firePropertyChange((new Integer(taskId)).toString(),prev,state);
  }
  String getErrorMessages() {
    return(errorMessages);
  }
  void clearErrorMessages() {
    errorMessages="";
  }
  void setState(int state,String stateInfo) {
    String prev = this.stateInfo;
    log(TaskManager.stateName(state)+":"+stateInfo);
    if(state==TaskManager.STATE_ERROR) {
      errorMessages+=stateInfo+"\n";
    }
    setState(state);
    this.stateInfo = stateInfo;
    firePropertyChange((new Integer(taskId)).toString(),prev,stateInfo);
  }
  int getState() {
    return(state);
  }
  void setStateInfo(String stateInfo) {
    String prev = this.stateInfo;
    this.stateInfo = stateInfo;
    firePropertyChange((new Integer(taskId)).toString(),prev,stateInfo);
  }
  String getStateInfo() {
    return(stateInfo);
  }
  void setProgress(int progress,int progressTotal) {
    int prev = this.progress;
    this.progress = progress;
    this.progressTotal = progressTotal;
    firePropertyChange((new Integer(taskId)).toString(),prev,progress);
  }
  void setProgress(int progress) {
    int prev = this.progress;
    this.progress = progress;
    firePropertyChange((new Integer(taskId)).toString(),prev,progress);
  }
  void setProgress() {
    firePropertyChange((new Integer(taskId)).toString(),progress,++progress);
  }
  void setThread(Thread thread) {
    this.thread = thread;
  }
  Thread getThread() {
    return(thread);
  }

  int getProgress() {
    return(progress);
  }
  int getProgressTotal() {
    return(progressTotal);
  }
  void setException(Exception exception) {
    Exception prev = this.exception;
    this.exception = exception;
    firePropertyChange((new Integer(taskId)).toString(),prev,exception);
  }
  Exception getException() {
    return(exception);
  }
  void setTaskProcess(TaskProcess taskProcess) {
    this.taskProcess = taskProcess;
  }
  void invoke() {
    Thread worker = new Thread() {
        public void run() {
          try {
            try {
              if(parentTaskId!=0) {
                Task task = TaskManager.getTask(parentTaskId);
                while( task.isAlive() ||
                       (!subTask && task.hasSubTaskAlive())) {
                  try {
                    Thread.sleep(100);
                  } catch(InterruptedException e) { }
                }
                if(relyOnParent &&
                    (task.getState()!=TaskManager.STATE_DONE) &&
                    (task.getState()!=TaskManager.STATE_RUN)) {
                  setState(TaskManager.STATE_ERROR,"relying on failed task");
                  return;
                }
              }
            } catch (ConsoleException e) {}
            timeStart=Calendar.getInstance();
            taskProcess.start(taskId);
            if(TaskManager.getTask(taskId).getState()==TaskManager.STATE_RUN) {
              setState(TaskManager.STATE_DONE);
            }
            unregisterListeners();
          } catch (Exception e) {
            setException(e);
            setState(TaskManager.STATE_ERROR);
          }
        }
      };
    setThread(worker);
    worker.start();
  }
  synchronized boolean hasSubTaskAlive() {
    for(Task t : TaskManager.taskList) {
      if(t.subTask &&
         (t.getParentTaskId()==taskId) &&
         (t.isAlive() ||
          t.hasSubTaskAlive())) {
        return(true);
      }
    }
    return(false);
  }
  boolean isAlive() {
    if((thread!=null) &&
        (thread.getState()==Thread.State.TERMINATED)) {
      return(false);
    }
    if(exception!=null) {
      return(false);
    }
    return(true);
  }
  void incCPUTime(long cpuTime) {
    this.cpuTime+=cpuTime;
  }
  long getCPUTime() {
    return(cpuTime);
  }
  void setLogFile(File file) {
    logFile = file;
  }
  void log(String message) {
    FileOutputStream f;
    if(logFile!=null) {
      try {
        f= new FileOutputStream(logFile,true);
      } catch (FileNotFoundException e) {
        System.out.println("could not open file:"+logFile.getName());
        return;
      }
      PrintStream s = new PrintStream(f);
      s.println(message);
      s.flush();
      s.close();
    }
  }
  void setParentTaskId(int parentTaskId) {
    this.parentTaskId = parentTaskId;
  }
  int getParentTaskId() {
    return(parentTaskId);
  }
  synchronized void registerListeners() {
    for(PropertyChangeListener pcl :TaskManager.listenerList) {
      addPropertyChangeListener(pcl);
    }
  }
  synchronized void unregisterListeners() {
    for(PropertyChangeListener pcl :TaskManager.listenerList) {
      removePropertyChangeListener(pcl);
    }
  }
}

public class TaskManager {
  public static final int REQUEST_OTHER=0;
  public static final int REQUEST_RUN=1;
  public static final int REQUEST_PAUSE=2;
  public static final int REQUEST_CANCEL=-1;

  public static final int STATE_OTHER=0;
  public static final int STATE_RUN=1;
  public static final int STATE_PAUSE=2;
  public static final int STATE_DONE=3;
  public static final int STATE_FAIL=4;
  public static final int STATE_ERROR=-1;

  static Vector<PropertyChangeListener> listenerList = new Vector<PropertyChangeListener>();
  static Vector<Task> taskList = new Vector<Task>();
  static int taskIdCounter=0;
  static Task defaultTask = new Task("Console",0);
  static {
    try {
      setLogFile(0,"task-manager.log",false);
    } catch (ConsoleException e) {
      System.err.println("failed to set task manager log file");
    }
  }

  static String requestName(int request) {
    switch(request) {
      case(REQUEST_OTHER):
        return("OTHER ");
      case(REQUEST_RUN):
        return("RUN   ");
      case(REQUEST_PAUSE):
        return("PAUSE ");
      case(REQUEST_CANCEL):
        return("CANCEL");
      default:
        return("------");
    }
  }
  static String stateName(int state) {
    switch(state) {
      case(STATE_OTHER):
        return("OTHER");
      case(STATE_RUN):
        return("RUN  ");
      case(STATE_PAUSE):
        return("PAUSE");
      case(STATE_DONE):
        return("DONE ");
      case(STATE_FAIL):
        return("FAIL ");
      case(STATE_ERROR):
        return("ERROR");
      default:
        return("-----");
    }
  }
  static public void setLogFile(int taskId,String fileName,boolean append) throws ConsoleException {
    File f;
    if(fileName==null) {
      return;
    }
    try {
      f = new File(fileName);
      if(!append) {
        f.delete();
      }
      getTask(taskId).setLogFile(f);
    } catch (ConsoleException e) {
      throw new ConsoleException("can't set log file, "+e.getMessage());
    }
  }
  static public void log(int taskId,String message) throws ConsoleException {
    try {
      getTask(taskId).log(message);
    } catch (ConsoleException e) {
      throw new ConsoleException("can't log message, "+e.getMessage());
    }
  }
  synchronized static public int newTask(String name) {
    //Add a new task gui to the task manager gui
    Task task = new Task(name,++taskIdCounter);
    taskList.add(task);
    return(taskIdCounter);
  }
  synchronized static Task getTask(int taskId) throws ConsoleException {
    Task task;
    if(taskId==0) {
      return(defaultTask);
    }
    for(int i=0;i<taskList.size();i++) {
      task = taskList.elementAt(i);
      if(task.getTaskId()==taskId) {
        return(task);
      }
    }
    throw new ConsoleException("invalid task id:"+taskId);
  }
  static public int getState(int taskId) throws ConsoleException {
    try {
      return(getTask(taskId).getState());
    } catch (ConsoleException e) {
      throw new ConsoleException("can't get state, "+e.getMessage());
    }
  }
  static public void setState(int taskId,int state) throws ConsoleException {
    try {
      getTask(taskId).setState(state);
    } catch (ConsoleException e) {
      throw new ConsoleException("can't set state, "+e.getMessage());
    }
  }
  static public void setState(int taskId,int state,String stateInfo) throws ConsoleException {
    try {
      getTask(taskId).setState(state,stateInfo);
    } catch (ConsoleException e) {
      throw new ConsoleException("can't set state, "+e.getMessage());
    }
  }
  static public int getRequest(int taskId) throws ConsoleException {
    try {
      return(getTask(taskId).getRequest());
    } catch (ConsoleException e) {
      throw new ConsoleException("can't get request, "+e.getMessage());
    }
  }
  static public void setRequest(int taskId,int state) throws ConsoleException {
    try {
      getTask(taskId).setRequest(state);
    } catch (ConsoleException e) {
      throw new ConsoleException("can't set request, "+e.getMessage());
    }
  }
  static public void setStateInfo(int taskId,String stateInfo) throws ConsoleException {
    try {
      getTask(taskId).setStateInfo(stateInfo);
    } catch (ConsoleException e) {
      throw new ConsoleException("can't set state information, "+e.getMessage());
    }
  }
  static public String getStateInfo(int taskId) throws ConsoleException {
    try {
      return(getTask(taskId).getStateInfo());
    } catch (ConsoleException e) {
      throw new ConsoleException("can't get state information, "+e.getMessage());
    }
  }
  static public void setProgress(int task_id, int progress,int progressTotal) throws ConsoleException {
    getTask(task_id).setProgress(progress,progressTotal);
  }
  static public void setProgress(int task_id,int progress) throws ConsoleException {
    getTask(task_id).setProgress(progress);
  }
  static public void setProgress(int task_id) throws ConsoleException {
    getTask(task_id).setProgress();
  }
  static public int getProgress(int task_id) throws ConsoleException {
    return(getTask(task_id).getProgress());
  }
  static public int getProgressTotal(int task_id) throws ConsoleException {
    return(getTask(task_id).getProgressTotal());
  }

  static Calendar timeCount(long time) {
    long hour;
    long minute;
    long second;
    hour=time/(60*60);
    time-=hour*60*60;
    minute=time/(60);
    time-=minute*60;
    second=time;
    Calendar c = Calendar.getInstance();
    c.set(Calendar.HOUR_OF_DAY,(int)hour);
    c.set(Calendar.MINUTE,(int)minute);
    c.set(Calendar.SECOND,(int)second);
    return(c);
  }
  synchronized static public void showTaskList() {
    int i;
    Task task;
    SimpleDateFormat f = new SimpleDateFormat("HH:mm:ss");
    System.out.println("################################################");
    for(i=0;i<=taskList.size();i++) {
      if(i==taskList.size()) {
        task = defaultTask;
      } else {
        task = taskList.elementAt(i);
      }
      int progress = task.getProgress();
      int progressTotal = task.getProgressTotal();
      String p = "#"+task.getTaskId()+" ("+task.getParentTaskId()+") "+task.name;
      String q;
      if(task.getThread()!=null) {
        q=" "+task.getThread().getState();
      } else {
        q="";
      }
      while((p.length()+q.length())<48) { q = " " + q; }
      System.out.println(p+q);
      p = "("+progress+"/"+progressTotal+")";
      while(p.length()<14) { p = " " + p; }
      if(task.getStateInfo()!=null) {
        System.out.println("\t\""+task.getStateInfo()+ "\"");
      }
      System.out.println("\tRequest:"+requestName(task.getRequest())+" State:"+stateName(task.getState())+p);
      if(task.timeStart!=null) {
        System.out.print("started: "+f.format(task.timeStart.getTime()));
        if(task.timeEnd!=null) {
          try {
          System.out.println(", ended: "+f.format(task.timeEnd.getTime()));
          } catch(java.lang.IllegalArgumentException e) {
            System.out.println("error"+task.timeEnd + "  xx " + task.timeEnd.getTime());
          }
        } else {
          System.out.println();
        }
      } else {
        if(task.timeEnd!=null) {
          try {
          System.out.println("ended:"+f.format(task.timeEnd));
          } catch(java.lang.IllegalArgumentException e) {
            System.out.println("error"+task.timeEnd + "  xx " + task.timeEnd.getTime());
          }
        }
      }
      if(task.getCPUTime()>0) {
        System.out.println("\tcpu time:  "+f.format(timeCount(task.getCPUTime()/1000).getTime()));
      }
    }
    if(i>0) {
      System.out.println("################################################");
    }
  }
  static public void setThread(int taskId,Thread thread) throws ConsoleException {
    try {
      getTask(taskId).setThread(thread);
    } catch (ConsoleException e) {
      throw new ConsoleException("can't set thread, "+e.getMessage());
    }
  }
  static public Thread getThread(int taskId) throws ConsoleException {
    try {
      return(getTask(taskId).getThread());
    } catch (ConsoleException e) {
      throw new ConsoleException("can't get thread, "+e.getMessage());
    }
  }


  static public Exception getException(int taskId) throws ConsoleException {
    try {
      return(getTask(taskId).getException());
    } catch (ConsoleException e) {
      throw new ConsoleException("can't get exception, "+e.getMessage());
    }
  }


  static public void setException(int taskId, Exception exception) throws ConsoleException {
    try {
      getTask(taskId).setException(exception);
    } catch (ConsoleException e) {
      throw new ConsoleException("can't set exception, "+e.getMessage());
    }
  }
  static public long getCPUTime(int taskId) throws ConsoleException {
    try {
      return(getTask(taskId).getCPUTime());
    } catch (ConsoleException e) {
      throw new ConsoleException("can't get CPU time, "+e.getMessage());
    }
  }
  static public String getErrorMessages(int taskId) throws ConsoleException {
    try {
      return(getTask(taskId).getErrorMessages());
    } catch (ConsoleException e) {
      throw new ConsoleException("can't get error messages, "+e.getMessage());
    }
  }

  static public void incCPUTime(int taskId, long cpuTime) throws ConsoleException {
    try {
      getTask(taskId).incCPUTime(cpuTime);
    } catch (ConsoleException e) {
      throw new ConsoleException("can't increment CPU time, "+e.getMessage());
    }
  }


  static public void setTaskProcess(int taskId, TaskProcess taskProcess) throws ConsoleException {
    try {
      getTask(taskId).setTaskProcess(taskProcess);
    } catch (ConsoleException e) {
      throw new ConsoleException("can't set task process, "+e.getMessage());
    }
  }

  static public void invoke(int taskId) throws ConsoleException {
    try {
      getTask(taskId).invoke();
    } catch (ConsoleException e) {
      throw new ConsoleException("can't set task process, "+e.getMessage());
    }
  }
  static public boolean isAlive(int taskId) throws ConsoleException {
    return(getTask(taskId).isAlive());
  }
  static public void setParentTaskId(int taskId,int parentTaskId) throws ConsoleException {
    Task task;
    try {
      task = getTask(taskId);
      task.setParentTaskId(parentTaskId);
    } catch (ConsoleException e) {
      throw new ConsoleException("can't set parent task id, "+e.getMessage());
    }
  }
/*
  static public int exe(String name,TaskProcess taskProcess,int parentTaskId,boolean relyOnParent,boolean subTask) {
    int task_id = newTask(name);
    Task task;
    try {
      task = getTask(task_id);
      task.setParentTaskId(parentTaskId);
      task.relyOnParent = relyOnParent;
      task.subTask = subTask;
    } catch (ConsoleException e) {
      System.out.println("didn't get a task id from newTask");
      return(-1);
    }
    task.setState(STATE_RUN);
    task.setTaskProcess(taskProcess);
    task.invoke();
    return(task_id);
  }
*/
  static public int exe(String name,TaskProcess taskProcess,String logFile,int parentTaskId,boolean relyOnParent,boolean subTask) {
    int task_id = newTask(name);
    Task task;
    try {
      task = getTask(task_id);
      setLogFile(task_id,logFile,false);
      task.setParentTaskId(parentTaskId);
      task.relyOnParent = relyOnParent;
      task.subTask = subTask;
    } catch (ConsoleException e) {
      System.out.println("didn't get a task id from newTask");
      return(-1);
    }
    task.setState(STATE_RUN);
    task.setTaskProcess(taskProcess);
    task.invoke();
    return(task_id);
  }
  static public int exe(String name,TaskProcess taskProcess,int parentTaskId,boolean relyOnParent) {
    return(exe(name,taskProcess,null,parentTaskId,relyOnParent,false));
  }


  synchronized static public boolean clearTask(int taskId) throws ConsoleException {
    try {
      Task task = getTask(taskId);
      int state = task.getState();
      if(!isAlive(taskId)) {
        taskList.remove(task);
        return(true);
      } else {
        return(false);
      }
    } catch (ConsoleException e) {
      throw new ConsoleException("can't remove task, "+e.getMessage());
    }
  }
  synchronized static public void clearTaskList() {
    int canClearAll = 0;
    for(int i=0;i<taskList.size();i++) {
      try {
        if(clearTask(taskList.elementAt(i).getTaskId())) {
          i=-1;
        }
      } catch (ConsoleException e) {}
    }
  }
  static void addListener(PropertyChangeListener pcl) {
    defaultTask.addPropertyChangeListener(pcl);
    listenerList.add(pcl);
  }
}
